package org.vic.strategy.demo03.improve.annotation;

import org.vic.strategy.demo03.CalPrice;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

/**
 * 处理注解返回相应价格计算策略的工厂类
 * Created by VIC.Huang on 2016/8/24.
 */
public class CalPriceFactoryHandler {
    // 单例模式
    private CalPriceFactoryHandler(){
        init();
    }

    private static class CalPriceFactoryHandlerInstance {
        private static CalPriceFactoryHandler instance = new CalPriceFactoryHandler();
    }

    public static CalPriceFactoryHandler getInstance() {
        return CalPriceFactoryHandlerInstance.instance;
    }

    private static final String CAL_PRICE_PAGEAGE = "org.vic.strategy.demo03.improve.annotation";   // 指定扫描的策略所在包名
    private ClassLoader classLoader = getClass().getClassLoader();      //我们加载策略时的类加载器，我们任何类运行时信息必须来自该类加载器
    private List<Class<? extends  CalPrice>> calPriceList;         // 策略列表

    // 在工厂初始化时要初始化策略列表
    private void init() {
        calPriceList = new ArrayList<Class<? extends CalPrice>>();
        File[] resources = getResources();      // 获取包下所有的class文件
        Class<CalPrice> calPriceClazz = null;
        try {
            calPriceClazz = (Class<CalPrice>)classLoader.loadClass(CalPrice.class.getName());   // 使用相同的加载器加载策略接口
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("未找到策略接口！");
        }
        for(int i=0; i<resources.length; i++) {
            try {
                // 载入包下的类
                Class<?> clazz = classLoader.loadClass(CAL_PRICE_PAGEAGE + "." + resources[i].getName().replace(".class", ""));
                // 判断是否是CalPrice的实现类并且不是CalPrice本身，满足的话加入策略列表集合中
                if(CalPrice.class.isAssignableFrom(clazz) && clazz != calPriceClazz) {
                    calPriceList.add((Class<? extends CalPrice>)clazz);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("载入类失败！");
            }
        }
    }

    // 获取扫描的包下所有的class文件
    private File[] getResources() {
        try {
            File file = new File(classLoader.getResource(CAL_PRICE_PAGEAGE.replace(".", "/")).toURI());
            return file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    if(pathname.getName().endsWith(".class")) {
                        return true;
                    }else {
                        return false;
                    }
                }
            });
        } catch (URISyntaxException e) {
            throw new RuntimeException("未找到策略资源！");
        }
    }

    // 根据客户的总金额产生对应的价格计算策略
    public CalPrice createCalPrice(Customer customer) {
        // 在策略列表中查找适应的策略
        for(Class<? extends CalPrice> clazz : calPriceList) {
            TotalValidRegion validRegion = handleAnnotation(clazz);
            if(validRegion == null) {
                continue;
            }
            // 判断金额是否在注解区间内
            if(customer.getTotalAmount() >= validRegion.min() && customer.getTotalAmount() < validRegion.max()) {
                try {
                    // 是则返回当前策略类的实例
//                    return clazz.newInstance();
                    return clazz.getDeclaredConstructor(DiffType4CalPrice.class).newInstance(DiffType4CalPrice.class.newInstance());
                } catch (Exception e) {
                    throw new RuntimeException("获取策略失败！");
                }
            }
        }
        throw new RuntimeException("获取策略失败！");
    }

    // 传入类，处理返回它对应的注解
    private TotalValidRegion handleAnnotation(Class<? extends CalPrice> clazz) {
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        if(annotations == null || annotations.length == 0) {
            return null;
        }
        for(Annotation anno : annotations) {
            if(anno instanceof TotalValidRegion) {
                return (TotalValidRegion)anno;
            }
        }
        return null;
    }
}
